#include "StdAfx.h"
#include "MeshModel.h"
#include "vec.h"
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>

using namespace std;

struct FaceIdcs
{
	int v[4];
	int vn[4];
	int vt[4];

	FaceIdcs()
	{
		for (int i=0; i<4; i++)
			v[i] = vn[i] = vt[i] = 0;
	}

	FaceIdcs(std::istream & aStream)
	{
		for (int i=0; i<4; i++)
			v[i] = vn[i] = vt[i] = 0;

		char c;
		for(int i = 0; i < 3; i++)
		{
			aStream >> std::ws >> v[i] >> std::ws;
			if (aStream.peek() != '/')
				continue;
			aStream >> c >> std::ws;
			if (aStream.peek() == '/')
			{
				aStream >> c >> std::ws >> vn[i];
				continue;
			}
			else
				aStream >> vt[i];
			if (aStream.peek() != '/')
				continue;
			aStream >> c >> vn[i];
		}
	}
};

vec3 vec3fFromStream(std::istream & aStream)
{
	float x, y, z;
	aStream >> x >> std::ws >> y >> std::ws >> z;
	return vec3(x, y, z);
}

vec2 vec2fFromStream(std::istream & aStream)
{
	float x, y;
	aStream >> x >> std::ws >> y;
	return vec2(x, y);
}

MeshModel::MeshModel(string fileName)
{
	loadFile(fileName);
	this->_world_transform = identity4();
	this->m_cameraReversalMatrix = identity4();
	this->m_origin = vec4(0,0,0,1);
	m_renderer = NULL;
	m_camera = NULL;
}

MeshModel::~MeshModel(void)
{
}



void MeshModel::loadFile(string fileName)
{
	ifstream ifile(fileName.c_str());
	vector<FaceIdcs> faces;
	vector<vec3> vertices;
	vector<vec3> normals;
	vector<vec2> texture;
	// while not end of file
	while (!ifile.eof())
	{
		// get line
		string curLine;
		getline(ifile, curLine);

		// read type of the line
		istringstream issLine(curLine);
		string lineType;

		issLine >> std::ws >> lineType;

		// based on the type parse data
		if (lineType == "v")
			vertices.push_back(vec3fFromStream(issLine));
		else if (lineType == "vn")
			normals.push_back(vec3fFromStream(issLine));
		else if (lineType == "vt")
			texture.push_back(vec2fFromStream(issLine));
		else if (lineType == "f")
			faces.push_back(issLine);
		else if (lineType == "#" || lineType == "")
		{
			// comment / empty line
		}
		else
		{
			cout<< "Found unknown line Type \"" << lineType << "\"";
		}
	}
	_vertices = vector<vec3>(faces.size()*3);
	_normals = vector<vec3>(faces.size()*3);
	// iterate through all stored faces and create triangles
	int k=0;
	vector<FaceIdcs>::iterator tmp_it = faces.begin();
	boundingBoxMax = vec3(vertices[tmp_it->v[0]-1].x,vertices[tmp_it->v[0]-1].y,vertices[tmp_it->v[0]-1].z);
	boundingBoxMin = vec3(INFINITE,INFINITE,INFINITE);
	
	for (vector<FaceIdcs>::iterator it = faces.begin(); it != faces.end(); ++it){

		for (int i = 0; i < 3; i++)
		{
			if(it->v[i] != 0){
				_vertices[k] = vertices[it->v[i]-1];
			}
			if(it->vn[i] != 0){
				_normals[k] = normals[it->vn[i]-1];
			}	
			if(boundingBoxMax.x < _vertices[k].x){boundingBoxMax.x = _vertices[k].x;}
			if(boundingBoxMax.y < _vertices[k].y){boundingBoxMax.y = _vertices[k].y;}
			if(boundingBoxMax.z < _vertices[k].z){boundingBoxMax.z = _vertices[k].z;}
			if(boundingBoxMin.x > _vertices[k].x){boundingBoxMin.x = _vertices[k].x;}
			if(boundingBoxMin.y > _vertices[k].y){boundingBoxMin.y = _vertices[k].y;}
			if(boundingBoxMin.z > _vertices[k].z){boundingBoxMin.z = _vertices[k].z;}
			k++;
		}
	}
	modelDiameter = length(vec4(boundingBoxMax-boundingBoxMin,0));
}

void MeshModel::draw()
{
	this->m_renderer->SetObjectMatrices(this->_world_transform,NULL);
	this->m_renderer->setNormalLength(modelDiameter);
	this->m_renderer->DrawTriangles(&(this->_vertices),NULL);
	this->m_renderer->drawVertexNormals(&(this->_vertices),&(this->_normals));
	this->m_renderer->drawBoundingBox(boundingBoxMin, boundingBoxMax);
}

void MeshModel::ApplyTransformation(const mat4 &  m){
	_world_transform = m*_world_transform;
	this->m_origin = m*this->m_origin; // update the origin location...
}

void MeshModel::TranslateModel(const GLfloat & x,const GLfloat & y,const GLfloat & z) {
	mat4 m = Translate(x,y,z);
	this->ApplyTransformation(m);
}

void MeshModel::SpinModel(const GLfloat & x, const GLfloat & y, const GLfloat & z){
	mat4 x_rotator = RotateX(x);
	mat4 y_rotator = RotateY(y);
	mat4 z_rotator = RotateZ(z);
	mat4 to_world_origin_translator = Translate(-m_origin);
	mat4 to_object_origin_translator = Translate(m_origin);
	mat4 m = (to_object_origin_translator*(x_rotator*(y_rotator*(z_rotator*to_world_origin_translator))));
	ApplyTransformation(m);
}

void MeshModel::SetCamera(Camera * c) {
	this->ApplyTransformation(this->m_cameraReversalMatrix);
	this->m_cameraReversalMatrix = c->getCorrector();
	m_camera = c;
	this->ApplyTransformation(c->getCameraTransform());
}

void MeshModel::ScaleMe(const GLfloat & x, const GLfloat & y, const GLfloat & z){
	//we got to first move to the origin, and then scale,  I guess...
	mat4 m = Translate(m_origin) * Scale(x,y,z) * Translate(-m_origin);
	ApplyTransformation(m);
}

/*
this is pure hell...
*/
vec4 MeshModel::getOriginInWorld() const {
	Camera * c = this->m_camera;
//	if (c->isOrtho()) {
//		vec4 ret = c->getProjectionTransform() * m_origin;
//		ret.perspectiveDivide();
//		return m_cameraReversalMatrix * ret;
//	}
	return this->m_cameraReversalMatrix * m_origin;
}

/* I assume this method is called right after loading the model.
	in case called after transformations, the result might be devastating...
*/
void MeshModel::Centralize() {
	m_origin = vec3((boundingBoxMin.x + boundingBoxMax.x)/2,
					(boundingBoxMin.y + boundingBoxMax.y)/2,
					(boundingBoxMin.z + boundingBoxMax.z)/2);
	this->TranslateModel(-m_origin.x,-m_origin.y,-m_origin.z);
}

void MeshModel::Shrink() {
	GLfloat width, height, depth;
	width  = boundingBoxMax.x - boundingBoxMin.x;
	height = boundingBoxMax.y - boundingBoxMin.y;
	depth = boundingBoxMax.z - boundingBoxMin.z;
	GLfloat maxVal = (width > depth) ? (width > height ? width : height) : (depth > height ? depth : height);
	GLfloat coeff = 2.0/maxVal;
	if (coeff < 1) this->ScaleMe(coeff,coeff,coeff);
}